/*
 * ContextoPPMC.cpp
 *
 *  Created on: 28/04/2012
 *      Author: lucas
 */

#include "../hdr/EmisorAritmetico.h"
#include "../hdr/ContextoPPMC.h"
#include <stdio.h>

#define ESC -1
#define LIMITE_PUNTAJE 65535

ContextoPPMC::ContextoPPMC() {

}

ContextoPPMC::~ContextoPPMC() {

}

/* Para calcular el piso puedo ir hasta el iPiso. El ESC esta siempre en la posicion 0
 * del vector, por lo tanto, piso = 0 en ese caso.
 */

/* excluidos es el vector con los caracteres que deben ser excluidos en la emision del caracter.
 * En proxExclusion pone la direccion de memoria del vector de caracteres de este contexto,
 * para que sea usado en el contexto siguiente como vector de excluidos.
 */
int ContextoPPMC::emitirProbabilidad(unsigned short caracter,
		char* proxExclusion, EmisorAritmetico* emisor) {

	// Debug
//	printf("Excluidos: ");
//	for (int i = 0; i < 256; i++)
//		if (proxExclusion[i] != 0)
//			printf("%c - ", i);
//	printf("\n");

	unsigned int total = 0, probabilidad = 0, piso = 0, auxProb;
	int resultado = 0; // = 0 si emite escape, sino = 1
	size_t largo = caracteres.getLargo();
	size_t i;
	for (i = 0; i < largo; i++) {
		short caracter_actual = caracteres[i];
		if ((caracter_actual != ESC) && (proxExclusion[caracter_actual] != 0)) // Exclusion
			continue;
		auxProb = getProbabilidad(i);
		total += auxProb;
		if (caracter_actual != caracter) {
			if (caracter_actual == ESC) {
				if (probabilidad == 0)
					probabilidad = auxProb;
			} else {
				proxExclusion[caracter_actual] = 1;
			}
		} else {
			piso = total - auxProb;
			probabilidad = auxProb;
			resultado = 1;
		}
	}

	if (probabilidad == 0) {
		probabilidad = 1;
		total = 1;
	}
	if (resultado == 0)
		piso = 0;

	// Debug
	/*
	 if (resultado == 0)
	 printf("Emite escape (%c) con probabilidad %d/%d\n", caracter,
	 probabilidad, total);
	 else
	 printf("Emite '%c' con probabilidad %d/%d (piso %d)\n", caracter,
	 probabilidad, total, piso);
	 */
	// Fin debug
	// Piso: suma de las frecuencias previas
	// largo: frecuencia del caracter
	// total: suma de todas las frecuencias
	if (probabilidad < total)
		emisor->emitirSimbolo(piso, probabilidad, total);
	// Devuelve los caracteres para la exclusion del proximo modelo

	if (actualizarContexto(caracter) == -1)
		return -1;
	return resultado;
}

int ContextoPPMC::actualizarContexto(unsigned char caracter) {
	size_t posicion = 0;
	int posicionEscape = -1;
	while (posicion < caracteres.getLargo()) {
		if (caracteres[posicion] == caracter) {
			break;
		}
		if (caracteres[posicion] == ESC)
			posicionEscape = posicion;
		posicion++;
	}
	if (posicion == caracteres.getLargo()) { // No encontró el caracter
		if (posicionEscape == -1) { // No encontró el ESC <=> Está inicializando el contexto
			if (caracteres.append(ESC) == -1)
				return -1;
			if (frecuencias.append(1) == -1)
				return -1;
			agregarCaracterEnvejecimiento();
		} else { // Encontró el ESC => actualiza la frecuencia
			frecuencias[posicionEscape]++;
			actualizarEnvejecimiento(posicionEscape);
		}
		if (caracteres.append(caracter) == -1) // Inicializa el caracter y su frecuencia
			return -1;
		if (frecuencias.append(1) == -1)
			return -1;
		agregarCaracterEnvejecimiento();
	} else { // Encontró el caracter => actualiza la frecuencia
		frecuencias[posicion]++;
		actualizarEnvejecimiento(posicion);
	}

	// DEBUG
	/*
	 printf("Actualiza: %c\n", caracter);
	 for (unsigned int i = 0; i < caracteres.getLargo(); i++) {
	 printf("\t'%c':\t%d\t- Envejec: %d\t- frec: %d\n", caracteres[i], frecuencias[i],
	 envejecimiento[i], frecPosicion[i]);
	 }
	 */
	//
	if (debeRenormalizar())
		renormalizar();

	// Debug
//	printf("Actualiza: %c\n", caracter);
//	for (unsigned int i = 0; i < caracteres.getLargo(); i++) {
//		printf("\t'%c':\t%d\n", caracteres[i], frecuencias[i]);
//	}

	return 0;
}

/* Obtiene la probabilidad del caracter como producto de su frecuencia
 * y la frecuencia de ocurrencia de esa posicion. 
 * 'indice' es el indice del caracter en el vector de caracteres. */
unsigned int ContextoPPMC::getProbabilidad(unsigned int indice) {
	return 4 * frecuencias[indice] + frecPosicion[envejecimiento[indice]];
}

/* Actualiza los envejecimiento, y agrega un nuevo caracter
 * en la ultima posicion tomando como que fue lo ultimo que 
 * ocurrio. */
void ContextoPPMC::agregarCaracterEnvejecimiento() {
	for (unsigned int i = 0; i < envejecimiento.getLargo(); i++)
		envejecimiento[i]++;
	envejecimiento.append(0);
	frecPosicion.append(1);
}

/* Actualiza el envejecimiento, tomando lo apuntado por 
 * 'posicion' como lo ultimo que ocurrio.
 * Pre: el vector NO puede estar vacio.
 * 0 <= posicion < envejecimiento.getLargo() */
void ContextoPPMC::actualizarEnvejecimiento(int posicion) {
	int valor = envejecimiento[posicion];
	int contador = 0;
	size_t largo = envejecimiento.getLargo();
	for (size_t i = 0; i < largo; i++) {
		if (envejecimiento[i] < valor) {
			envejecimiento[i]++;
			contador++;
		}
		if (contador == valor) // Me ahorra recorrer todo el
			break; // vector en ciertos casos
	}
	frecPosicion[valor]++;
	envejecimiento[posicion] = 0;
}

/* 'caracter' es una direccion de memoria valida para escribir el caracter procesado,
 * en caso de hallarlo. En 'excluidos' estan los caracteres del contexto superior
 * que deben ser excluidos en este. 
 * Devuelve 0 si emite ESC, 1 si no. */
int ContextoPPMC::emitirCaracter(unsigned char* caracter,
		Vector<short> *excluidos, Vector<short> **proxExclusion,
		LectorAritmetico *lector) {
	if (frecuencias.getLargo() == 0) { // Ctx no inicializado
		// Debo invocar SI O SI a leerSimbolo, porque este lee el archivo
//		printf("Emite ESC de una y al toque... pa\n");
		return 0;
	}
	Vector<short> auxCaracteres;
	size_t largo = frecuencias.getLargo();
	unsigned short largos[largo];
	unsigned short cantidad = 0, total = 0, indice, auxProb;
	for (unsigned int i = 0; i < largo; i++) {
		short caracter_actual = caracteres[i];
		if (excluidos && caracter_actual != ESC
				&& excluidos->contiene(caracter_actual)) // Exclusion
			continue;
		auxProb = getProbabilidad(i);
		largos[cantidad] = auxProb;
		total += auxProb;
		auxCaracteres.append(caracter_actual);
		cantidad++;
//		printf("Pone caracter (%c) - frecuencia (%d)\n", caracteres[i],
//				(int) frecuencias[i]); // DEBUG
	}
	indice = lector->leerSimbolo(largos, total, cantidad);
//	printf("Luego de leerSimbolo, devuelve el indice (%d)\n", (int) indice); // DEBUG
	// Devuelve los caracteres para la exclusion del proximo modelo
	*proxExclusion = &caracteres;
	// ESC siempre es indice = 0
	if (indice > 0) {
		*caracter = auxCaracteres[indice];
//		printf("El caracter levantado del archivo es el (%c)\n", *caracter);
		return 1;
	}
	return 0;
}

unsigned int ContextoPPMC::getCantidadDeCaracteres() {
	if (caracteres.getLargo() == 0)
		return 0;
	return caracteres.getLargo() - 1;
}

unsigned int ContextoPPMC::getCantidadDeCaracteresMenores(
		unsigned short caracter) {
	unsigned int resultado = 0;
	for (unsigned int i = 0; i < caracteres.getLargo(); i++) {
		if (caracteres[i] < caracter && caracteres[i] != ESC)
			resultado++;
	}
	return resultado;
}

bool ContextoPPMC::debeRenormalizar() {
	int puntaje = 0;
	unsigned char frec, freclargo;
	size_t largo = frecuencias.getLargo();
	for (unsigned int i = 0; i < largo; i++) {
		frec = frecuencias[i];
		freclargo = frecPosicion[envejecimiento[i]];
		if (frec == 255 || freclargo == 255) {
			return true;
		}
		puntaje += 4 * frec + freclargo;
	}
	if (puntaje > LIMITE_PUNTAJE)
		return true;
	return false;
}

void ContextoPPMC::renormalizar() {
	int puntaje = 100000;
	while (puntaje > LIMITE_PUNTAJE) {
		puntaje = 0;
		for (unsigned int i = 0; i < frecuencias.getLargo(); i++) {
			frecuencias[i] = frecuencias[i] / 2 + 1;
			frecPosicion[envejecimiento[i]] = frecPosicion[envejecimiento[i]] / 2 + 1;
			puntaje += getProbabilidad(i);
		}
	}
}

